/*
 * Zed Attack Proxy (ZAP) and its related class files.
 *
 * ZAP is an HTTP/HTTPS proxy for assessing web application security.
 *
 * Copyright 2012 The ZAP Development Team
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.zaproxy.zap.extension.autoupdate;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.Proxy;
import java.net.URL;
import java.util.Date;
import org.apache.log4j.Logger;
import org.zaproxy.zap.utils.HashUtils;

public class Downloader extends Thread {
    private URL url;
    private Proxy proxy;
    private File targetFile;
    private Exception exception = null;
    private long size = 0;
    private boolean complete = false;
    private Date started = null;
    private Date finished = null;
    private boolean cancelDownload = false;
    private String hash = null;
    private boolean validated = false;

    private static final Logger logger = Logger.getLogger(Downloader.class);

    public Downloader(URL url, Proxy proxy, File targetFile, String hash) {
        this(url, proxy, targetFile, 0, hash);
    }

    public Downloader(URL url, Proxy proxy, File targetFile, long size, String hash) {
        super();
        this.url = url;
        this.proxy = proxy;
        this.targetFile = targetFile;
        this.size = size;
        this.hash = hash;
    }

    @Override
    public void run() {
        this.started = new Date();

        if (hash != null) {
            if (hash.indexOf(":") > 0) {
                downloadFile();
                if (!cancelDownload) {
                    validateHashDownload();
                }
            } else {
                logger.debug(
                        "Not downloading file, hash field does not have valid content (\"<ALGORITHM>:<HASH>\"): "
                                + hash);
            }
        } else {
            logger.debug("Not downloading file, does not have a hash: " + url);
        }

        this.complete = true;
        this.finished = new Date();
        if (cancelDownload) {
            this.targetFile.delete();
        }
    }

    private void downloadFile() {
        BufferedInputStream in = null;
        FileOutputStream out = null;
        try {
            /*
             * The following code may be more efficient, but doesn't give us the option
             * of cancelling downloads.
             *
             * ReadableByteChannel rbc = Channels.newChannel(url.openStream());
             * FileOutputStream fos = new FileOutputStream(targetFile);
             * fos.getChannel().transferFrom(rbc, 0, 1 << 24);
             */

            // XXX Change to use HttpClient instead of URL to download the file. The
            // java.net.Authenticator is shared by all
            // the URLConnection, it may be changed by 3rd party add-ons/libraries and it can't be
            // set on a single connection
            // (see bug 4941958 [1]) in which case the authentication will not succeed (hence the
            // file will not be downloaded).
            //
            // [1] http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4941958

            in = new BufferedInputStream(url.openConnection(proxy).getInputStream());
            out = new FileOutputStream(this.targetFile);
            byte[] data = new byte[1024];
            int count;
            while (!cancelDownload && (count = in.read(data, 0, 1024)) != -1) {
                out.write(data, 0, count);
            }
        } catch (Exception e) {
            this.exception = e;
        } finally {
            try {
                if (in != null) {
                    in.close();
                }
                if (out != null) {
                    out.close();
                }
            } catch (IOException e) {
                // Ignore
            }
        }
    }

    private void validateHashDownload() {
        try {
            String algorithm = hash.substring(0, hash.indexOf(":"));
            String hashValue = hash.substring(hash.indexOf(":") + 1);
            String realHash = HashUtils.getHash(targetFile, algorithm);
            if (realHash.equalsIgnoreCase(hashValue)) {
                validated = true;
            } else {
                logger.debug("Wrong hash - expected " + hashValue + " got " + realHash);
            }
        } catch (Exception e) {
            // Ignore - we default to unvalidated
            logger.debug("Error checking hash", e);
        }
    }

    boolean isCancelled() {
        return cancelDownload;
    }

    public void cancelDownload() {
        this.cancelDownload = true;
        if (complete && this.targetFile.exists()) {
            this.targetFile.delete();
        }
    }

    public Exception getException() {
        return exception;
    }

    public URL getUrl() {
        return url;
    }

    public File getTargetFile() {
        return targetFile;
    }

    public int getProgressPercent() {
        if (complete) {
            return 100;
        }
        if (this.size == 0) {
            return 0;
        }
        return (int) (this.targetFile.length() * 100 / this.size);
    }

    public Date getStarted() {
        return started;
    }

    public Date getFinished() {
        return finished;
    }

    public String getHash() {
        return hash;
    }

    public boolean isValidated() {
        return validated;
    }
}
